package hu.imind.phonecalc.client;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;

import com.extjs.gxt.ui.client.Style.HorizontalAlignment;
import com.extjs.gxt.ui.client.Style.LayoutRegion;
import com.extjs.gxt.ui.client.event.ComponentEvent;
import com.extjs.gxt.ui.client.event.Events;
import com.extjs.gxt.ui.client.event.Listener;
import com.extjs.gxt.ui.client.event.SelectionChangedEvent;
import com.extjs.gxt.ui.client.event.SelectionChangedListener;
import com.extjs.gxt.ui.client.event.WidgetListener;
import com.extjs.gxt.ui.client.store.ListStore;
import com.extjs.gxt.ui.client.util.Margins;
import com.extjs.gxt.ui.client.widget.ContentPanel;
import com.extjs.gxt.ui.client.widget.Dialog;
import com.extjs.gxt.ui.client.widget.LayoutContainer;
import com.extjs.gxt.ui.client.widget.Status;
import com.extjs.gxt.ui.client.widget.TabItem;
import com.extjs.gxt.ui.client.widget.TabPanel;
import com.extjs.gxt.ui.client.widget.Viewport;
import com.extjs.gxt.ui.client.widget.button.Button;
import com.extjs.gxt.ui.client.widget.form.DateField;
import com.extjs.gxt.ui.client.widget.form.FormPanel;
import com.extjs.gxt.ui.client.widget.form.HiddenField;
import com.extjs.gxt.ui.client.widget.form.SimpleComboBox;
import com.extjs.gxt.ui.client.widget.form.SimpleComboValue;
import com.extjs.gxt.ui.client.widget.form.TextArea;
import com.extjs.gxt.ui.client.widget.grid.ColumnConfig;
import com.extjs.gxt.ui.client.widget.grid.ColumnData;
import com.extjs.gxt.ui.client.widget.grid.ColumnModel;
import com.extjs.gxt.ui.client.widget.grid.Grid;
import com.extjs.gxt.ui.client.widget.grid.GridCellRenderer;
import com.extjs.gxt.ui.client.widget.layout.BorderLayout;
import com.extjs.gxt.ui.client.widget.layout.BorderLayoutData;
import com.extjs.gxt.ui.client.widget.layout.FitLayout;
import com.extjs.gxt.ui.client.widget.toolbar.FillToolItem;
import com.extjs.gxt.ui.client.widget.toolbar.LabelToolItem;
import com.extjs.gxt.ui.client.widget.toolbar.ToolBar;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;

import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;

import hu.imind.phonecalc.client.dto.Call;
import hu.imind.phonecalc.client.dto.Contact;
import hu.imind.phonecalc.client.dto.LoginInfo;

/**
 * Entry point classes define <code>onModuleLoad()</code>.
 */
public class Imind_phonecalc implements EntryPoint {
    private MyStatus status;
    private ContentPanel centerPanel;
    private TabPanel tabPanel;
    private TextArea callSourceTextField;
    private TextArea workNumbersTextField;
    private TextArea privNumbersTextField;
    private SimpleComboBox<String> savedBills;
    private DateField callListDate;
    private ListStore<Call> callStore;
    private Grid<Call> callsGrid;
    private ListStore<Contact> contactStore;
    private Grid<Contact> contactsGrid;

    private LoginInfo loginInfo;

    private float totalUnkown = 0;
    private float totalWork = 0;
    private float totalPriv = 0;

    private ContactServiceAsync contactService;

    /* maps numbers to contacts. The bill processing looks for the names here */
    private HashMap<String, Contact> contactCache = new HashMap<String, Contact>();
    /* contains the work or priv numbers, the bill and the contact list processing looks up the numbers here */
    private HashSet<String> workNums = new HashSet<String>();
    private HashSet<String> privNums = new HashSet<String>();


    /**
     * This is the entry point method.
     */
    public void onModuleLoad() {
        Viewport viewport = new Viewport();
        viewport.setLayout(new FitLayout());

        viewport.add(getMyRootPanel());
        RootPanel.get().add(viewport);
        checkLoginStatus();
    }

    private void checkLoginStatus() {
        // Check login status using login service.
        status.setBusy("Requesting login information...");
        LoginServiceAsync loginService = GWT.create(LoginService.class);
        loginService.login(GWT.getHostPageBaseURL(), new AsyncCallback<LoginInfo>() {
            public void onFailure(Throwable error) {
                status.clearStatus("");
                handleError(error);
            }

            public void onSuccess(LoginInfo result) {
                status.clearStatus("");
                loginInfo = result;
                status.setAuth(loginInfo);
                if (loginInfo.isLoggedIn()) {
                    if (loginInfo.getWorkNumbers() != null) {
                        workNumbersTextField.setValue(loginInfo.getWorkNumbers());
                        setAndCacheNumbers(workNumbersTextField, loginInfo.getWorkNumbers(), workNums);
                    }
                    if (loginInfo.getPrivNumbers() != null) {
                        privNumbersTextField.setValue(loginInfo.getPrivNumbers());
                        setAndCacheNumbers(privNumbersTextField, loginInfo.getPrivNumbers(), privNums);
                    }
                    List<String> x = loginInfo.getBills();
                    if (x != null) {
                        for (String s : x) {
                            savedBills.add(s);
                        }
                    }
                    contactService = GWT.create(ContactService.class);
                    if (loginInfo.isContactAuthorized()) {
                        loadContacts();
                    } else {
                        // open the login dialog
                        final Dialog loginDialog = new Dialog();  
                        loginDialog.setHeading("Login required");  
                        loginDialog.setButtons(Dialog.CANCEL);  
                        loginDialog.addText("<br />You are not logged in. <a href='" + loginInfo.getAuthSubLoginUrl() + "'>Use this url</a> to " +
                                "autenticate at google, and give permission" +
                        "to read your contacts data.<br /><br />");  
                        loginDialog.setHideOnButtonClick(true);
                        loginDialog.show();
                    }
                } else {
                    // open the login dialog
                    final Dialog loginDialog = new Dialog();  
                    loginDialog.setHeading("Login required");  
                    loginDialog.setButtons(Dialog.OK);  
                    loginDialog.addText("<br />You are not logged in. <a href='" + loginInfo.getLoginUrl() + "'>Use this url</a> to " +
                    "autenticate at google.<br /><br />");  
                    loginDialog.setHideOnButtonClick(true);
                    loginDialog.addListener(Events.Close, new WidgetListener() {
                        @SuppressWarnings("unused")
                        public void handleEvent(Widget sender) {
                            Window.Location.replace(loginInfo.getLoginUrl());
                        }
                    });
                    loginDialog.show();
                }
            }
        });
    }

    protected void loadContacts() {
        status.setBusy("Loading google contacts...");
        contactService.getContacts(new AsyncCallback<List<Contact>>() {
            public void onFailure(Throwable error) {
                handleError(error);
            }
            public void onSuccess(List<Contact> contacts) {
                status.setBusy("Processing contacts...");
                for (Contact contact : contacts) {
                    for(int i = 1; i < 6; i++) {
                        if (contact.get("phone" + i) != null) {
                            String num = contact.get("phone" + i).toString();
                            contactCache.put(num, contact);
                            if (workNums != null && workNums.contains(num)) {
                                contact.setType("C");
                            } else if (privNums != null && privNums.contains(num)) {
                                contact.setType("P");
                            } else {
                            }
                        } else {
                            break;
                        }
                    }
                }
                contactStore.add(contacts);
                status.setContactNum();
                status.clearStatus("");
            }
        });
    }


    private void handleError(Throwable error) {
        Window.alert(error.getMessage());
    }

    private void refreshPhoneType(TextArea textArea, String type, HashSet <String> cache) {
        cache.clear();
        String s = textArea.getValue();
        status.setBusy("Refreshing contacts data...");
        StringBuffer sb = new StringBuffer();
        if (s != null) {
            String[] nums = s.split("\\s");
            for (String num : nums) {
                if (num.length() == 0) {
                    continue;
                }
                num = Contact.formatPhoneNumber(num);
                cache.add(num);
                sb.append(num + "\n");
                Contact c = contactCache.get(num);
                if (c != null) {
                    c.setType(type);
                } else {
                    Contact newC = new Contact("", num, type);
                    contactStore.add(newC);
                    contactCache.put(num, newC);
                }
            }
        }
        status.clearStatus("");
        status.setContactNum();
        textArea.setValue(sb.toString());
    }
    
    private void setAndCacheNumbers(TextArea ta, String numbers, HashSet<String> cache) {
        ta.setValue(numbers);
        int count = 0;
        String[] nums = numbers.split("\\n");
        cache.clear();
        for (String num : nums) {
            if (num.length() == 0) {
                continue;
            }
            count++;
            cache.add(num);
        }
    }

    @SuppressWarnings("deprecation")
    private void processCallSource() {
        String s = callSourceTextField.getValue();
        if (s == null || s.length() == 0) {
        } else {
            String[] rows = s.split("\\n");
            callStore.removeAll();
            ArrayList<Call> newCalls = new ArrayList<Call>(); 
            for (String row : rows) {
                try{
                    String[] fields = row.split(" *\\t *");
                    String num = Contact.formatPhoneNumber(fields[0]); 
                    String name = "";
                    String cat = "";
                    
                    // TODO ne a contact-ból szedjük a type-ot, hanem a numberCache-ből 
                    Contact c = contactCache.get(num);
                    if (c != null) {
                        if (c.getName().length() > 0) {
                            name = c.getName();
                        }
                        if (c.getType() != null) {
                            cat = c.getType();
                        }
                    }
                    
                    Call newCall = new Call(num, name, fields[1], fields[2], fields[6], fields[8], cat);
                    newCalls.add(newCall);
                    if (newCall.getCat() == "") {
                        totalUnkown += newCall.getTotal();
                    } else {
                        switch (newCall.getCat().charAt(0)) {
                        case 'C': totalWork += newCall.getTotal(); break;
                        case 'P': totalPriv += newCall.getTotal(); break;
                        default: totalUnkown += newCall.getTotal(); break;
                        }
                    }
                }catch (Exception e) {
                    System.err.println("imind phonecalc.processCallSource exception: " + e.getMessage() + " row: " + row);
                }
            }
            callStore.add(newCalls);
            centerPanel.setHeading("Work: " +  (int)totalWork + 
                    " Priv: " + (int)totalPriv + 
                    " Uknown: " + (int)totalUnkown + 
                    " To Pay: " + (int)(totalUnkown + totalPriv));
        }
        
        // save the numbers
        status.setStatus("Saveing numbers");
        java.util.Date date = callListDate.getValue();
        String sd = "" + (date.getYear() + 1900) + "." + date.getMonth() + "." + date.getDate();
        contactService.saveNumbers(workNumbersTextField.getValue(), privNumbersTextField.getValue(), callStore.getModels(), sd,   
                new AsyncCallback<Boolean>() {
                        public void onFailure(Throwable error) {
                            status.clearStatus("");
                            handleError(error);
                        }
                        public void onSuccess(Boolean result) {
                            if (result) {
                                status.clearStatus("Numbers saved");
                            } else {
                                status.clearStatus("Error saveing numbers");
                            }
                        }
                }
        );
    }

    protected void loadBill(String date) {
        status.setBusy("Loading call list: " + date);
        contactService.getBill(date,   
                new AsyncCallback<List<Call>>() {
                        public void onFailure(Throwable error) {
                            status.clearStatus("Loading call list failed!");
                            handleError(error);
                        }

                        public void onSuccess(List<Call> result) {
                            if (result != null) {
                                callStore.add(result);
                            }
                            status.clearStatus("Call list loaded");
                        }
                }
        );
    }

    public LayoutContainer getMyRootPanel() {
        BorderLayoutData west = new BorderLayoutData(LayoutRegion.WEST, 320);  
        west.setCollapsible(true);  
        west.setSplit(true);  

        BorderLayoutData center = new BorderLayoutData(LayoutRegion.CENTER);  
        center.setMargins(new Margins(0, 0, 0, 5));  

        BorderLayoutData south = new BorderLayoutData(LayoutRegion.SOUTH);
        south.setSize(24);

        LayoutContainer container = new LayoutContainer();
        container.setLayout(new BorderLayout());

        container.add(getContactsPanel(), west);
        container.add(getCenterPanel(), center);
        container.add(status = new MyStatus(), south);
        return container;
    }

    private Widget getContactsPanel() {
        GridCellRenderer<Contact> phoneRenderer = new GridCellRenderer<Contact>() {  
            public String render(Contact model, String property, ColumnData config, int rowIndex,  
                    int colIndex, ListStore<Contact> store, Grid<Contact> grid) {
                int count = 0;
                String result = "";
                for (int i = 1; i < 6; i++) {
                    String s = model.get("phone" + i);
                    if (s == null) {
                        break;
                    }
                    if (s.length() > 0) {
                        if (count > 0) {
                            result += "<br />";
                        }
                        result += s;
                        count ++;
                    }
                }
                return result;  
            }
        };  


        List<ColumnConfig> configs = new ArrayList<ColumnConfig>();  

        ColumnConfig column = new ColumnConfig();  
        column.setId("name");  
        column.setHeader("Name");  
        column.setWidth(120);  
        configs.add(column);  

        column = new ColumnConfig();  
        column.setId("phone");  
        column.setHeader("Phone");  
        column.setWidth(100);  
        column.setRenderer(phoneRenderer);
        configs.add(column);  

        column = new ColumnConfig();  
        column.setId("type");  
        column.setHeader("Type");  
        column.setWidth(50);  
        configs.add(column);  

        contactStore = new ListStore<Contact>();

        ColumnModel cm = new ColumnModel(configs);  

        ContentPanel contentPanel = new ContentPanel();  
        contentPanel.setBodyBorder(false);  
        contentPanel.setHeading("Contacts");  
        contentPanel.setButtonAlign(HorizontalAlignment.CENTER);  
        contentPanel.setLayout(new FitLayout());  

        contactsGrid = new Grid<Contact>(contactStore, cm);  
        contactsGrid.setStyleAttribute("borderTop", "none");  
        contactsGrid.setAutoExpandColumn("name");  
        contactsGrid.setBorders(true);  
        contentPanel.add(contactsGrid);         
        return contentPanel;
    }

    private Widget getCenterPanel() {
        centerPanel = new ContentPanel();  
        centerPanel.setBodyBorder(false);  
        centerPanel.setHeading("Data");  
        centerPanel.setButtonAlign(HorizontalAlignment.CENTER);  
        centerPanel.setLayout(new FitLayout());
        centerPanel.add(getTabPanel());
        return centerPanel;
    }

    private Widget getTabPanel() {
        tabPanel = new TabPanel();

        TabItem tabCalls = new TabItem("Result");
        tabCalls.setLayout(new FitLayout());
        tabCalls.add(getResultFormTab());
        tabPanel.add(tabCalls);

        TabItem tabCallSource = new TabItem("Call Source");
        tabCallSource.setLayout(new FitLayout());
        tabCallSource.add(getCallSourceTab());
        tabPanel.add(tabCallSource);

        TabItem tabWorkNumbers = new TabItem("Work Numbers");  
        tabWorkNumbers.setLayout(new FitLayout());
        tabWorkNumbers.add(getWorkNumbersTab());
        tabPanel.add(tabWorkNumbers);

        TabItem tabPrivNumbers = new TabItem("Private Numbers");  
        tabPrivNumbers.setLayout(new FitLayout());
        tabPrivNumbers.add(getPrivNumbersTab());
        tabPanel.add(tabPrivNumbers);

        return tabPanel;
    }

    private Widget getResultFormTab() {
        List<ColumnConfig> configs = new ArrayList<ColumnConfig>();  
        
        GridCellRenderer<Call> phoneNumRenderer = new GridCellRenderer<Call>() {  
            public String render(Call model, String property, ColumnData config, int rowIndex,  
                    int colIndex, ListStore<Call> store, Grid<Call> grid) {
                String name = model.get("name");
                if (name != null && name.length() > 0) {
                    return name;
                } else {
                    return model.get("num");
                }
            }
        };    

        ColumnConfig column = new ColumnConfig();  
        column.setId("num");  
        column.setHeader("Phone Number");  
        column.setWidth(150);
        column.setResizable(true);
        column.setRenderer(phoneNumRenderer);
        configs.add(column);  

        column = new ColumnConfig();  
        column.setId("cat");  
        column.setHeader("Category");  
        column.setWidth(20);  
        column.setResizable(true);
        configs.add(column);  

        column = new ColumnConfig();  
        column.setId("date");  
        column.setHeader("Date");  
        column.setWidth(110);  
        column.setResizable(true);
        configs.add(column);  

        column = new ColumnConfig();  
        column.setId("duration");  
        column.setHeader("Duration");  
        column.setWidth(60);  
        column.setResizable(true);
        configs.add(column);  

        column = new ColumnConfig();  
        column.setId("price");  
        column.setHeader("Price /m");  
        column.setWidth(60);  
        column.setResizable(true);
        configs.add(column);  

        column = new ColumnConfig();  
        column.setId("total");  
        column.setHeader("Total");  
        column.setWidth(60);  
        column.setResizable(true);
        configs.add(column);  


        callStore = new ListStore<Call>();

        ColumnModel cm = new ColumnModel(configs);  

        callsGrid = new Grid<Call>(callStore, cm);  
        callsGrid.setStyleAttribute("borderTop", "none");  
        //callsGrid.setAutoExpandColumn("num");  
        callsGrid.setBorders(true);  

        LayoutContainer container = new LayoutContainer();
        container.setLayout(new BorderLayout());

        BorderLayoutData center = new BorderLayoutData(LayoutRegion.CENTER);  
        //center.setMargins(new Margins(0, 0, 0, 5));  

        BorderLayoutData north = new BorderLayoutData(LayoutRegion.NORTH);
        north.setSize(24);
        
        
        savedBills = new SimpleComboBox<String>();
        savedBills.addSelectionChangedListener(
                new SelectionChangedListener<SimpleComboValue<String>>() {
                    @Override
                    public void selectionChanged(SelectionChangedEvent<SimpleComboValue<String>> se) {
                        loadBill(se.getSelectedItem().getValue());
                    }
        });


        container.add(callsGrid, center);
        container.add(savedBills, north);
        
        return container;
    }

    private Widget getCallSourceTab() {
        FormPanel formPanel = new FormPanel();  
        formPanel.setHeading("Call list from the provider");
        formPanel.setLayout(new BorderLayout());

        BorderLayoutData north = new BorderLayoutData(LayoutRegion.NORTH, 30);
        BorderLayoutData center = new BorderLayoutData(LayoutRegion.CENTER);  
        BorderLayoutData south = new BorderLayoutData(LayoutRegion.SOUTH, 30);  

        callListDate = new DateField();
        callListDate.setFieldLabel("The list's date");
        formPanel.add(callListDate, north);
        
        callSourceTextField = new TextArea();
        callSourceTextField.setHeight(200);
        callSourceTextField.setFieldLabel("Call Info Source Data");  
        formPanel.add(callSourceTextField, center);  

        Button b = new Button("Save");
        b.addListener(Events.OnMouseUp, new Listener<ComponentEvent>() {
            public void handleEvent(ComponentEvent e) {
                processCallSource();
            }
        });
        formPanel.add(b, south);  
        
        return formPanel;
    }

    private Widget getWorkNumbersTab() {
        workNumbersTextField = new TextArea();
        workNumbersTextField.setHeight(200);
        workNumbersTextField.setFieldLabel("Phone numbers");  
        return getTabForm("C", "Work Phone Numbers", workNumbersTextField, workNums);
    }

    private Widget getPrivNumbersTab() {
        privNumbersTextField = new TextArea();
        privNumbersTextField.setHeight(200);
        privNumbersTextField.setFieldLabel("Phone numbers");  
        return getTabForm("P", "Private Phone Numbers", privNumbersTextField, privNums);
    }
    
    private Widget getTabForm(final String type, String typeString, final TextArea textArea, final HashSet<String> numCache) {
        FormPanel formPanel = new FormPanel();  
        formPanel.setHeading(typeString);
        formPanel.setLayout(new BorderLayout());

        BorderLayoutData south = new BorderLayoutData(LayoutRegion.SOUTH, 30);  
        BorderLayoutData center = new BorderLayoutData(LayoutRegion.CENTER);  
        
        Button b = new Button("Save");
        b.addListener(Events.OnMouseUp, new Listener<ComponentEvent>() {
            public void handleEvent(ComponentEvent e) {
                refreshPhoneType(textArea, type, numCache);
                contactsGrid.getView().refresh(false);
                processCallSource();
            }
        });
        formPanel.add(b, south);  

        formPanel.add(textArea, center);  

        
        HiddenField<String> hidden = new HiddenField<String>();
        hidden.setValue(type);
        formPanel.add(hidden);
        
        return formPanel;
    }

    class MyStatus extends ToolBar {
        private Status status;
        private Status contactNum;
        private Status authenticated;
        private Status authorized;

        public MyStatus() {
            status = new Status();  
            status.setText("Ready...");
            add(status);  
            add(new FillToolItem());

            contactNum = new Status();  
            contactNum.setWidth(70);  
            contactNum.setText("Contacts: 0");  
            contactNum.setBox(true);  
            add(contactNum);  

            add(new LabelToolItem(" "));  

            authenticated = new Status();  
            authenticated.setWidth(200);  
            authenticated.setText("Not Logged In");  
            authenticated.setBox(true);  
            add(authenticated);  

            add(new LabelToolItem(" "));  

            authorized = new Status();  
            authorized.setWidth(120);  
            authorized.setText("Contacts not authorized");  
            authorized.setBox(true);  
            add(authorized);  
        }
        public void setStatus(String s) {
            status.setText(s);
        }
        public void clearStatus(String s) {
            status.clearStatus(s);
        }
        public void setBusy(String s) {
            status.setBusy(s);
        }
        public void setContactNum() {
            contactNum.setText("Contacts: " + contactStore.getCount());
        }
        public void setAuth(LoginInfo loginInfo) {
            if (loginInfo.getEmailAddress() != null) {
                authenticated.setText(loginInfo.getEmailAddress());
            } else {
                authenticated.setText("Not logged in");
            }
            if (loginInfo.isContactAuthorized()) {
                authorized.setText("Contacts authorized");
            } else {
                authorized.setText("Contacts not authorized");
            }
        }
    }
}
